home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-10-13 | 43.0 KB | 1,324 lines | [TEXT/MPS ] |
- PROGRAM Sample;
-
- (******************************************************************************
- *
- * Apple Macintosh Developer Technical Support
- *
- * Main program for the Sample application
- *
- * Program: Sample 3.0
- * file: Sample.p - Pascal implementation
- *
- * by: Matt Deatherage
- *
- * Copyright © 1988-1993 Apple Computer, Inc.
- * All rights reserved.
- *
- *******************************************************************************
- *
- * Sample.p contains the main entry point and the PROGRAM statement, but
- * relies heavily on other units linked in to implement its functionality.
- *
- * For non-technical details and the story behind this sample, see the
- * "Read Me - Sample" file distributed with this program.
- *
- * Segmentation strategy:
- *
- * Sample consists of four segments. "Main" contains most of the code used
- * to run the application, including any linked-in MPW or THINK libraries,
- * the main program, all routines in the TrafficLights unit and some
- * utility routines. "Dialogs" contains the code to do the major dialogs
- * the program presents -- the "Modify Circle" and "Edit Preferences" dialogs.
- * "Print" contains the code to handle printing, and that segment unloads
- * "Dialogs" to make more memory available for printing. "Initialize" contains
- * the code we use once, at application startup, and is then discarded.
- *
- * "Save and restore" strategy:
- *
- * Several system states or resources have only one current owner. These
- * include things like the system resource file, the current GrafPort, the
- * position of an open file, etc. Sometimes code carefully saves and restores
- * all resources it uses to prevent problems where a system state changes
- * when you don't expect it.
- *
- * Sample generally takes the strategy of using what it needs. If it needs
- * to draw into a GrafPort, it sets the current port to that GrafPort. This
- * makes the program less vulnerable to things like old desk accessories that
- * change the GrafPort behind the application's back, but more importantly, it
- * means any part of our program that draws or needs a specific GrafPort
- * doesn't have to worry about what it already was. We set it to what we
- * need each time.
- *
- * There are exceptions to this. We have some routines which are called by
- * the system, or by other routines that shouldn't necessarily have to reset
- * the current GrafPort after each subroutine call. In those cases, parameters
- * are saved and restored, and the routines are marked as such.
- *
- * Why the inconsistency? Each way works better in certain cases. If you
- * stick dogmatically to one philosophy or the other, you often wind up
- * inserting more code than you need just to be safe. This is a pragmatic
- * approach that works pretty well for most situations.
- *
- * Memory strategy:
- *
- * Sample has windows, which have associated "document" structures hanging
- * off the window's refCon field. We allocate the space for the window
- * record and for the document, and both are (non-relocatable) pointers
- * in our heap. Window records have to be that way; document structures
- * are because we chose it for simplicity.
- *
- * If you do complex operations, you normally want to avoid pointers, using
- * relocatable handles instead. However, since Sample is a fairly simple
- * application, we just make sure our heap is very unfragmented most of the
- * time. We almost never lock handles for more than a few lines of code,
- * and all of our window records and document pointers are allocated low
- * in the heap. Segments load high, and the rest is free space for handles
- * belonging to our window's GrafPorts, to print records or to other
- * system items like resources. Most of our resources are purgeable as well.
- *
- ******************************************************************************)
-
- (*******************************************************************************
- * Used Units
- *******************************************************************************)
-
- USES Traps, Memory, Desk, DiskInit, OSEvents, Events, Fonts, ToolUtils, Scrap,
- Errors, Balloons, Features, PrintTraps, AppleTalk, Processes, PPCToolbox,
- EPPC, Notification, AppleEvents, SampleUtilities, TrafficLights,
- SampleDialog;
-
- (*******************************************************************************
- * Constants
- *******************************************************************************)
-
- CONST
-
- (*******************************************************************************
- * kNoEvents is the inverse of "every event" -- it means we don't want any
- * events. We use this with WaitNextEvent to let the toolbox fill in the
- * mouse position and tick count without extracting any events from the
- * event queue.
- *******************************************************************************)
-
- kNoEvents = 0; { no events mask }
-
- (*******************************************************************************
- * kMinHeap is the smallest heap we can possibly run in. If this result:
- *
- * ORD(GetApplLimit) - ORD(ApplicZone)
- *
- * is less than kMinHeap, we don't have enough memory to run. The
- * constant insures that we have enough memory for things that load
- * into our application heap under System 6 non-MultiFinder environments,
- * like reasonably-sized scraps, FKEYs and so forth. In Sample's case,
- * the number is so small (under 100K) that there's no way it could
- * fail to run without MultiFinder except on a seriously wacked-out
- * system.
- *
- * The number is largely determined by experience and looking at how much
- * memory you use in your heap, with debuggers and other heap examination
- * tools. There's no magic formula you can use to figure this out for
- * you; you have to dive in and figure how much memory you really need and
- * then pad it a little bit.
- *******************************************************************************)
-
- kMinHeap = 60 * 1024;
-
- (*******************************************************************************
- * kMinSpace is the minimum result from PurgeSpace, when called at initialization
- * time, for the application to run. This number acts as a double-check to
- * insure that there really is enough memory for the application to run,
- * including what has been taken up already by pre-loaded resources, the scrap,
- * code and other sundry memory blocks.
- *******************************************************************************)
-
- kMinSpace = 24 * 1024;
-
- (*******************************************************************************
- * kPrefSize is our preferred partition size, in KB.
- * kMinSize is our minimum parition size, in KB.
- *
- * You can find out more about these constants in Sample.h.
- *******************************************************************************)
-
- kPrefSize = 200;
- kMinSize = 180;
-
- (*******************************************************************************
- * kExtremeNeg and kExtremePos are used to set up wide-open rectangles and
- * regions. kExtremePos has a -1 attached for historical reasons; it works
- * around a very old region bug. It's not necessary anymore, but you might
- * see it, and that's why it's there. It doesn't hurt.
- *******************************************************************************)
-
- kExtremeNeg = - 32768;
- kExtremePos = 32767 - 1; { required for old region bug }
-
- (*******************************************************************************
- * Resource IDs, as found in Sample.r and Sample.h
- *******************************************************************************)
-
- rMenuBar = 128; { application's menu bar }
- rAboutAlert = 128; { about Alert }
-
- rFeaturesNotPresent = 1000; { Alert "some required features are missing" }
- rNoMemoryForApp = 1001; { Alert "Not enough memory to run" }
-
- (*******************************************************************************
- * The following constants are used to identify menus and their items. The menu IDs
- * have an "m" prefix and the item numbers within each menu have an "i" prefix.}
- *******************************************************************************)
-
- mApple = 128; { Apple menu }
- iAbout = 1;
-
- mFile = 129; { File menu }
- iNew = 1;
- iOpen = 2;
- iClose = 4;
- iSave = 5;
- iSaveAs = 6;
- iRevert = 7;
- iPageSetup = 9;
- iPrint = 10;
- iQuit = 12;
-
- mEdit = 130; { Edit menu }
- iUndo = 1;
- iCut = 3;
- iCopy = 4;
- iPaste = 5;
- iClear = 6;
- iPrefs = 8;
-
- (*******************************************************************************
- * These are for positioning the Disk Initialization dialogs.
- *******************************************************************************)
-
- kDITop = $0050;
- kDILeft = $0070;
-
- (*******************************************************************************
- * Global variables maintained by the program
- *******************************************************************************)
-
- VAR
-
- (*******************************************************************************
- * gInBackground is maintained by our osEvent handling routines. Any part of
- * the program can check it to find out if we're currently in the background.
- *******************************************************************************)
-
- gQuitting: BOOLEAN; { TRUE if we quit next time we go through
- the main event loop }
-
- gInBackground: BOOLEAN; { maintained by Initialize and DoEvent }
-
- cursorRgn: RgnHandle; { cursorRgn contains a copy of the last
- mouseRgn we passed to WaitNextEvent }
-
- (*******************************************************************************
- * Forward declarations
- *******************************************************************************)
-
- FUNCTION Terminate: BOOLEAN;
- FORWARD;
-
- (******************************************************************************
- *
- * Public: _DataInit
- *
- * This routine is in the %A5Init segment of the MPW Pascal library. We
- * reference it here so that we have an address inside that segment, and
- * we can unload it after we've launched to get it out of our heap.
- *
- * This does not compile if we're using THINK Pascal.
- *
- ******************************************************************************)
-
- {$IFC UNDEFINED THINK_PASCAL}
-
- PROCEDURE _DataInit;
- EXTERNAL;
-
- {$ENDC}
-
- {$S Main}
- (******************************************************************************
- *
- * Public: AdjustMenus
- *
- * AdjustMenus enables and disables menu items based on the current state.
- * The user can only select enabled menu items. We set up all the menu items
- * before we call MenuSelect or MenuKey. We also set them up when the
- * current window changes (activate, deactivate, close and open).
- *
- * Note that while MenuSelect is the only time the user will see menu items,
- * the user can see menu _titles_ at any time. This approach to deciding
- * what items to enable/disable for a menu has the advantage of concentrating
- * all decision-making in one routine. However, it has the disadvantage of
- * requiring this routine to make decisions about whether documents are
- * dirty or not to enable the right file-handling items. There are always
- * trade-offs.
- *
- ******************************************************************************)
-
- PROCEDURE AdjustMenus;
-
- VAR
- window: WindowPtr; { the frontmost window }
- menu: MenuHandle; { the menu we're adjusting }
- theDoc: DocumentPtr; { the frontmost window's document }
- dirtyFlag: INTEGER; { does the window need saving? }
- menuDirty: BOOLEAN; { do we need to redraw the menu bar? }
-
- BEGIN
- menuDirty := FALSE; { assume we don't need to redraw }
-
- window := FrontWindow;
- IF IsAppWindow(window) THEN
- BEGIN
- theDoc := DocumentPtr(GetWRefCon(FrontWindow));
- dirtyFlag := GetDocumentDirtyFlag(theDoc);
- END;
-
- { Do the File menu first. Enable "close" for DAs but not much
- else. }
-
- menu := GetMHandle(mFile); { can't close a window that doesn't exist }
- IF window = NIL THEN
- DisableItem(menu, iClose)
- ELSE
- EnableItem(menu, iClose);
-
- IF (IsDAWindow(window) OR (window = NIL)) THEN
- BEGIN { DAs can't use our menu items }
- DisableItem(menu, iPageSetup);
- DisableItem(menu, iPrint);
- DisableItem(menu, iSave);
- DisableItem(menu, iSaveAs);
- DisableItem(menu, iRevert);
- END
- ELSE IF IsAppWindow(window) THEN
- BEGIN
- EnableItem(menu, iPageSetup);
- EnableItem(menu, iPrint);
- EnableItem(menu, iSaveAs);
-
- { allow saving if the document is new or dirty, but only
- allow reverting if it's dirty, not if it's never been saved. }
-
- IF ((dirtyFlag = kDocumentDirty) AND (theDoc^.ourfileRefNum <> 0)) THEN
- EnableItem(menu, iRevert)
- ELSE
- DisableItem(menu, iRevert);
-
- IF (dirtyFlag <> kDocumentClean) THEN
- EnableItem(menu, iSave)
- ELSE
- DisableItem(menu, iSave);
- END;
-
- { next, the Edit menu. If a DA window is frontmost, we enable the
- standard editing items, but the only one we support is Copy, which
- copies an image of the document to the clipboard. }
-
- menu := GetMHandle(mEdit);
- IF IsDAWindow(window) THEN
- BEGIN { a desk accessory might need the edit menu }
- EnableItem(menu, iUndo);
- EnableItem(menu, iCut);
- EnableItem(menu, iCopy);
- EnableItem(menu, iPaste);
- EnableItem(menu, iClear);
- END
- ELSE
- BEGIN { but we know we do not }
- DisableItem(menu, iUndo);
- DisableItem(menu, iCut);
- IF window = NIL THEN
- DisableItem(menu, iCopy)
- ELSE
- EnableItem(menu, iCopy);
- DisableItem(menu, iClear);
- DisableItem(menu, iPaste);
- END;
-
- { Finally, the circle menu. If there's no application window, disable
- the whole menu. Otherwise, enable items as they relate to the front
- window. }
-
- menu := GetMHandle(mCircle);
- IF IsAppWindow(window) THEN
- BEGIN
- menuDirty := NOT BTST(menu^^.enableFlags,0);
- EnableItem(menu, 0); { This whole menu requires an app window
- frontmost}
- IF theDoc^.numCircles >= gPrefsRecord.maxNumCircles THEN
- DisableItem(menu, iAdd)
- ELSE
- EnableItem(menu, iAdd);
- IF theDoc^.numCircles = 1 THEN
- DisableItem(menu, iDelete)
- ELSE
- EnableItem(menu, iDelete);
- END
- ELSE
- BEGIN
- menuDirty := BTST(menu^^.enableFlags,0);
- DisableItem(menu, 0);
- END;
- IF menuDirty THEN
- DrawMenuBar;
- END; { AdjustMenus }
-
- {$S Main}
- (******************************************************************************
- *
- * Public: DoCloseWindow
- *
- * Closes a window. Rather than deal with dirty flags and other document-
- * specific stuff here, we call CloseAppWindow, which returns TRUE if the
- * window actually closed, just as this routine does.
- *
- * "action" is either kClosing or kQuitting, so the right alert can be
- * presented to the user.
- *
- ******************************************************************************)
-
- FUNCTION DoCloseWindow(window: WindowPtr; action: INTEGER): BOOLEAN;
-
- BEGIN
- DoCloseWindow := TRUE;
- IF IsDAWindow(window) THEN
- BEGIN
- CloseDeskAcc(WindowPeek(window)^.windowKind);
- END
- ELSE IF IsAppWindow(window) THEN
- BEGIN
- DoCloseWindow := CloseAppWindow(window, action);
- END;
- AdjustMenus;
-
- END; { DoCloseWindow }
-
- {$S Main}
- (******************************************************************************
- *
- * Public: SampleQuitHandler
- *
- * This routine handles the 'quit' Apple Event, one of the four required ones.
- * If we got all the parameters from the Apple event, we call Terminate.
- * If it returns TRUE, we return noErr, otherwise the user cancelled.
- *
- ******************************************************************************)
-
- FUNCTION SampleQuitHandler(quitAppleEvent: AppleEvent; reply: AppleEvent;
- handlerRefCon: LONGINT): OSErr;
-
- VAR
- myErr: OSErr; { errors from the system }
-
- BEGIN
- myErr := CheckRequiredAEParms(quitAppleEvent);
- IF myErr = noErr THEN
- BEGIN
- IF Terminate THEN
- myErr := noErr
- ELSE
- myErr := userCanceledErr;
- END;
- SampleQuitHandler := myErr;
- END; { SampleQuitHandler }
-
- {$S Main}
- (******************************************************************************
- *
- * Public: SampleOpenAppHandler
- *
- * This routine handles the 'oapp' Apple event, one of the four required ones.
- * If we got all the parameters from the event, we know we're not going to
- * open any documents so we call DoNew to create a new, untitled window.
- *
- ******************************************************************************)
-
- FUNCTION SampleOpenAppHandler(newAppleEvent: AppleEvent; reply: AppleEvent;
- handlerRefCon: LONGINT): OSErr;
-
- VAR
- myErr: OSErr; { errors from the system }
- theWindow: WindowPtr; { the window we create }
-
- BEGIN
- myErr := CheckRequiredAEParms(newAppleEvent);
- IF myErr = noErr THEN
- theWindow := DoNew;
- SampleOpenAppHandler := myErr;
- END;
-
- {$S Main}
- (******************************************************************************
- *
- * Public: SampleOpenDocHandler
- *
- * SampleOpenDocHandler handles the required 'odoc' event. This event is
- * supposed to pass a list of alias records as the direct object (though
- * AppleScript apparently passes a pathname as a 'TEXT' descriptor), and we
- * retrieve them as FSSpec records, which are identical to our fileLikeSpec
- * records. We then pass them to our DoOpenDocument routine to make windows
- * for them.
- *
- ******************************************************************************)
-
- FUNCTION SampleOpenDocHandler(openAppleEvent: AppleEvent; reply: AppleEvent;
- handlerRefCon: LONGINT): OSErr;
-
- VAR
- myErr: OSErr; { errors from the system }
- myDocList: AEDescList; { the list of descriptors }
- myFSSpec: FSSpec; { an FSSpec record for files }
- numItems: LONGINT; { how many files are there? }
- myKeyword: AEKeyword; { ignored -- for AEGetNthPtr }
- myType: DescType; { ignored -- real type of data returned }
- realSize: Size; { ignored -- real size of data returned }
- count: INTEGER; { loop variable counter }
- theWindow: WindowPtr; { the window we open }
-
- BEGIN
- myErr := AEGetParamDesc(openAppleEvent, keyDirectObject, typeAEList,
- myDocList);
- IF myErr = noErr THEN
- BEGIN
- myErr := CheckRequiredAEParms(openAppleEvent);
- IF myErr = noErr THEN
- BEGIN
- myErr := AECountItems(myDocList, numItems);
- IF myErr = noErr THEN
- BEGIN
- FOR count := 1 TO numItems DO
- BEGIN
- myErr := AEGetNthPtr(myDocList, count,
- typeFSS, myKeyword,
- myType, @myFSSpec,
- sizeof(FSSpec),
- realSize);
- IF myErr = noErr THEN
- theWindow := DoOpenDocument(
- fileLikeSpecPtr(@myFSSpec));
-
- END;
- END;
- END;
- END;
- SampleOpenDocHandler := myErr;
- END; { SampleOpenDocHandler }
-
- {$S Main}
- (******************************************************************************
- *
- * Public: SamplePrintDocHandler
- *
- * This routine handles the required 'pdoc' event by printing documents
- * passed in the direct object of the Apple event WITHOUT opening windows
- * for them.
- *
- * We get a new print record, initialize it and if we can interact with the
- * user, ask him to name his job-specific parameters. Then we process the
- * list of files in the Apple event like with SampleOpenDocHandler, but
- * this time we call DoPrintfile with the FSSpec and theMergeRec as a
- * PrJobMerge print record.
- *
- ******************************************************************************)
-
- FUNCTION SamplePrintDocHandler(printAppleEvent: AppleEvent; reply: AppleEvent;
- handlerRefCon: LONGINT): OSErr;
-
- VAR
- myErr: OSErr;
- myDocList: AEDescList;
- myFSSpec: FSSpec;
- numItems: LONGINT;
- myKeyword: AEKeyword;
- myType: DescType;
- realSize: Size;
- count: INTEGER;
- theDoc: DocumentPtr;
- theMergeRec: THPrint;
-
- BEGIN
- myErr := noErr; theMergeRec := THPrint(NewHandle(sizeof(TPrint)));
- IF theMergeRec <> NIL THEN
- BEGIN
- PrOpen;
- PrintDefault(theMergeRec);
- IF OKToInteract THEN
- IF NOT PrJobDialog(theMergeRec) THEN
- myErr := userCanceledErr;
- PrClose;
- IF myErr = noErr THEN
- BEGIN
- myErr := AEGetParamDesc(printAppleEvent, keyDirectObject,
- typeAEList, myDocList);
- IF myErr = noErr THEN
- BEGIN
- myErr := CheckRequiredAEParms(printAppleEvent);
- IF myErr = noErr THEN
- BEGIN
- myErr := AECountItems(myDocList, numItems);
- IF myErr = noErr THEN
- BEGIN
- FOR count := 1 TO numItems DO
-
- BEGIN
- myErr := AEGetNthPtr(myDocList, count,
- typeFSS, myKeyword, myType,
- @myFSSpec, sizeof(FSSpec),
- realSize);
-
- IF myErr = noErr THEN
- BEGIN
- DoPrintfile(fileLikeSpecPtr(@myFSSpec),
- theMergeRec);
- END;
-
- END;
- END;
- END;
- END;
- END;
- END;
- DisposeHandle(Handle(theMergeRec));
- SamplePrintDocHandler := myErr;
- END; { SamplePrintDocHandler }
-
- {$S Main}
- (******************************************************************************
- *
- * Public: RemoveAEHandlers
- *
- * This routine removes the four required event handlers, and then calls
- * RemoveAppAEHandlers to give other units a chance to remove any event handlers
- * they may have installed.
- *
- ******************************************************************************)
-
- PROCEDURE RemoveAEHandlers;
-
- VAR
- myErr: OSErr; { error from the Apple Event manager }
-
- BEGIN
- myErr := AERemoveEventHandler(kCoreEventClass, kAEOpenApplication,
- @SampleOpenAppHandler, FALSE);
- myErr := AERemoveEventHandler(kCoreEventClass, kAEOpenDocuments,
- @SampleOpenDocHandler, FALSE);
- myErr := AERemoveEventHandler(kCoreEventClass, kAEPrintDocuments,
- @SamplePrintDocHandler, FALSE);
- myErr := AERemoveEventHandler(kCoreEventClass, kAEQuitApplication,
- @SampleQuitHandler, FALSE);
- RemoveAppAEHandlers; { give the application a chance to remove more handlers
- without modifying Sample.p }
- END; { RemoveAEHandlers }
-
- {$S Main}
- (******************************************************************************
- *
- * Public: Terminate
- *
- * Terminate cleans up the application and exits. We loop through all open
- * windows, asking DoCloseWindow to close each one. If it ever returns
- * FALSE, we know a window couldn't be closed, so we abort the procedure.
- *
- * If all windows get closed, we call TerminateApplication to give other
- * units a chance to do cleanup as well. Then we remove our Apple Event
- * handlers by calling RemoveAEHandlers.
- *
- * The routine returns TRUE if we're ready to quit.
- *
- ******************************************************************************)
-
- FUNCTION Terminate: BOOLEAN;
-
- VAR
- aWindow: WindowPtr; { the window to close }
- closed,
- okToQuit: BOOLEAN;
-
- BEGIN
- closed := TRUE;
- REPEAT
- aWindow := FrontWindow; { get the current front window}
- IF aWindow <> NIL THEN
- closed := DoCloseWindow(aWindow, kQuitting);
- { close this window }
- UNTIL (NOT closed) | (aWindow = NIL); { do all windows }
-
- IF closed THEN
- BEGIN
- okToQuit := TerminateApplication;
- IF gHasAppleEvents THEN
- RemoveAEHandlers;
- gQuitting := (closed AND okToQuit);
- Terminate := gQuitting;
- END;
- END; { Terminate }
-
- {$S Initialize}
- (******************************************************************************
- *
- * Public: InstallAEHandlers
- *
- * This routine installs the four required event handlers, and then calls
- * InstallAppAEHandlers to give other units a chance to install any event handlers
- * they may need.
- *
- ******************************************************************************)
-
- PROCEDURE InstallAEHandlers;
-
- VAR
- myErr: OSErr; { error from the Apple Event manager }
-
- BEGIN
- myErr := AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
- @SampleOpenAppHandler, 0, FALSE);
- myErr := AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
- @SampleOpenDocHandler, 0, FALSE);
- myErr := AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments,
- @SamplePrintDocHandler, 0, FALSE);
- myErr := AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
- @SampleQuitHandler, 0, FALSE);
- InstallAppAEHandlers; { give the application a chance to install more
- handlers without modifying Sample.p }
- END;
-
- {$S Initialize}
- (******************************************************************************
- *
- * Public: Initalize
- *
- * Sets up the whole world. Initializes all the toolbox managers we use,
- * determines if the features we require to operate are present, checks to make
- * sure our heap is large enough to run, installs our menus, installs our
- * Apple Events handlers, and asks other units if they have anything to
- * initialize. If InitializeApplication returns FALSE, we terminate.
- *
- ******************************************************************************)
-
- PROCEDURE Initialize;
-
- VAR
- menuBar: Handle; { our menu bar 'MBAR' resource }
- total, { for PurgeSpace - all bytes available }
- contig: LONGINT; { for PurgeSpace - contiguous memory }
- ignoreResult: BOOLEAN; { for EventAvail below }
- event: EventRecord; { for EventAvail below }
- count: INTEGER; { loop counter variable }
-
- BEGIN
- gInBackground := FALSE;
- gQuitting := FALSE;
-
- { Allocate more master pointers. We empirically determine that if we
- opened the maximum number of windows our default partition allows, we
- need about 10 more master pointer blocks, so we create them here. }
-
- FOR count := 1 TO 10 DO
- MoreMasters;
-
- { Initialize the toolbox managers }
-
- InitGraf(@thePort);
- InitFonts;
- InitWindows;
- InitMenus;
- TEInit;
- InitDialogs(NIL);
- InitCursor;
-
- { You would initialize AppleTalk if you were using it at this Point.
-
- This magic incantation tells old MultiFinder that you're really alive
- and ready to go -- it waits for three event calls to make sure
- that you were serious about being an application and won't bring you
- to the front until it sees the third one. }
-
- FOR count := 1 TO 3 DO
- ignoreResult := EventAvail(everyEvent, event);
-
- { We call DetermineFeatures to see if we have the features we need. }
-
- IF NOT DetermineFeatures THEN
- AlertUser(rFeaturesNotPresent);
-
- { Check to make sure the heap we have meets our minimum heap requirements,
- in case the user cranked them down with ResEdit or something. If we
- don't have our minimum heap, we won't run. }
-
- IF ORD(GetApplLimit) - ORD(ApplicZone) < kMinHeap THEN
- AlertUser(rNoMemoryForApp);
-
- { Next, make sure that enough memory is free for your application to run. The
- heap may have been of required size, but a large scrap was loaded which left
- too little memory. To check for this, call PurgeSpace and compare the result
- with a value that you have determined is the minimum amount of free memory
- your application needs at initialization.
-
- This number can be derived several different ways. One way that is fairly
- straightforward is to run the application in the minimum size configuration
- as described previously. Call PurgeSpace at initialization and examine the value
- returned. However, you should make sure that this result is not being modified
- by the scrap's presence. You can do that by calling ZeroScrap before calling
- PurgeSpace. Make sure to remove that call before shipping, though. }
-
- PurgeSpace(total, contig);
- IF total < kMinSpace THEN
- AlertUser(rNoMemoryForApp);
-
- { Now get our menus installed and ready to go. }
-
- menuBar := GetNewMBar(rMenuBar); { read menus into menu bar }
- SetMenuBar(menuBar); { install menus }
- DisposeHandle(menuBar);
- AddResMenu(GetMHandle(mApple), 'DRVR');
- { add DA names to Apple menu}
- { install the Apple Event handlers }
-
- IF gHasAppleEvents THEN
- InstallAEHandlers;
-
- { Give another unit a chance to initialize itself }
-
- IF NOT InitializeApplication THEN
- ignoreResult := Terminate;
-
- AdjustMenus; { fix our menu items }
- DrawMenuBar; { force a draw the first time }
-
- END; { Initialize }
-
- {$S Main}
- (******************************************************************************
- *
- * Public: DoMenuCommand
- *
- * We call DoMenuCommand whenever the user chooses a menu item, with the mouse
- * or with a key equivalent. It performs the right operation for each command.
- * It keeps things organized when you have both MenuSelect and MenuKey dispatch
- * to one central command-handling routine like this.
- *
- * This routine actually handles the Edit menu commands, because they're so
- * brief.
- *
- ******************************************************************************)
-
-
- PROCEDURE DoMenuCommand(menuResult: LONGINT);
-
- VAR
- menuID: INTEGER; { the ID of the selected menu }
- menuItem: INTEGER; { the item number of the selected menu }
- daName: Str255; { name of the DA to open }
- daRefNum: INTEGER; { refNum of opened DA }
- handledByDA: BOOLEAN; { TRUE if an event was handled by a DA }
- ignore: BOOLEAN; { ignored result of function }
- theDoc: DocumentPtr; { document for a window }
- theWindow: WindowPtr; { for making a new window }
- ourPicture: PicHandle; { Picture to put in the scrap }
- lErr: LONGINT; { error from Scrap Manager }
- myHandle : Handle; { memory cushion Handle }
-
- BEGIN
-
- { Using HiWrd and LoWrd instead of HiWord and LoWord avoids going through
- the trap dispatcher in all cases, instead of relying on your development
- environment to do it }
-
- menuID := HiWrd(menuResult);
- menuItem := LoWrd(menuResult);
-
- CASE menuID OF
- mApple:
- CASE menuItem OF
- iAbout: { bring up Alert for About }
- AlertUser(rAboutAlert);
- OTHERWISE
- BEGIN { all non-About items in this menu are DAs }
- GetItem(GetMHandle(mApple), menuItem, daName);
- daRefNum := OpenDeskAcc(daName);
- END;
- END;
-
- mFile:
- CASE menuItem OF
- iNew:
- theWindow := DoNew;
- iOpen:
- theWindow := DoOpenDocument(NIL);
- iClose:
- ignore := DoCloseWindow(FrontWindow, kClosing);
- { we don't care if the user cancels this operation }
- iQuit:
- ignore := Terminate;
- iPageSetup:
- ignore := DoPageSetup(FrontWindow);
- iPrint:
- ignore := DoPrint(FrontWindow);
- iSave:
- ignore := DoSave(FrontWindow);
- iSaveAs:
- ignore := DoSaveAs(FrontWindow);
- iRevert:
- ignore := DoRevert(FrontWindow);
- END;
-
- mEdit: { call SystemEdit for DA editing &
- MultiFinder/Process Manager }
- CASE menuItem OF
- iPrefs:
- BEGIN
-
- { check to make sure there's enough memory to do
- the preferences dialog first! }
-
- myHandle := NewHandle(kDialogMemorySize);
- DisposeHandle(myHandle);
- IF myHandle = NIL THEN
- AlertUser(rNoMemoryForOperation)
- ELSE
- DoEditPreferences;
- END;
- iCopy:
- BEGIN
- IF IsDAWindow(FrontWindow) THEN
- handledByDA := SystemEdit(menuItem - 1)
- ELSE
- BEGIN
-
- { Get a PICT of our document and make it one of
- the scraps [is a boy dog] }
-
- ourPicture := MakeDocumentPicture(DocumentPtr(
- GetWRefCon(FrontWindow)));
- lErr := ZeroScrap;
- HLock(Handle(ourPicture));
- lErr := PutScrap(GetHandleSize(Handle(ourPicture)),
- 'PICT',
- StripAddress(Handle(ourPicture)^));
- END;
- END;
- OTHERWISE
- handledByDA := SystemEdit(menuItem - 1);
- { since we don't do any editing }
- END;
-
- mCircle:
- CASE menuItem OF
- iAdd:
- AddDefaultCircle(DocumentPtr(GetWRefCon(FrontWindow)),
- FrontWindow);
- iDelete:
- DeleteActiveCircle(FrontWindow);
- iModify:
- BEGIN
- theDoc := DocumentPtr(GetWRefCon(FrontWindow));
- SetPort(FrontWindow);
- IF ChangeCircleOptions(theDoc^.circleArray
- [theDoc^.activeCircle]) THEN
- SetDocumentDirtyFlag(theDoc, kDocumentDirty);
- END;
- END;
-
- OTHERWISE; { The System takes care of Apple, Help and Application menus
- for us when we call MenuSelect, but it's good form to have all
- cases covered with a statement. }
- END;
- HiliteMenu(0); { unhighlight what MenuSelect (or MenuKey) hilited }
- END; { DoMenuCommand }
-
- {$Z+} { makes this routine available for SampleUtilities.p }
- {$S Main}
- (******************************************************************************
- *
- * Public: DoUpdate
- *
- * This is called when we receive an update event for a window. It calls
- * DrawWindow to draw the contents of an application window. We only call
- * the routine if the visRgn is non-empty. Since BeginUpdate swaps the
- * updateRgn and the visRgn, the visRgn will be empty if there is no drawing
- * to do.
- *
- ******************************************************************************)
-
-
- PROCEDURE DoUpdate(window: WindowPtr);
-
- BEGIN
- IF IsAppWindow(window) THEN
- BEGIN
- BeginUpdate(window); { sets up the visRgn, clears updateRgn }
- IF NOT EmptyRgn(window^.visRgn) THEN
- { we pass FALSE because updating is not printing }
- DrawWindow(window, NIL, FALSE, window = FrontWindow);
- EndUpdate(window); { restores the visRgn }
- END;
- END; { DoUpdate }
-
- {$Z+} { makes this routine available for SampleUtilities.p }
- {$S Main}
- (******************************************************************************
- *
- * Public: DoActivate
- *
- * This is called when a window is activated or deactivated. in Sample, the
- * window Manager's handling of these events suits us just fine. Other
- * applications might have TextEdit records, controls, lists or other visual
- * things to redraw. We Handle this by invalidating the window and letting
- * our drawing routines redraw the window in an inactive state.
- *
- * We do _not_ invalidate if it's a context switch (a suspend or resume event).
- * Our front window looks the same when we're in the background, so invalidating
- * on suspend/resume events would cause an unnecessary blink. We similarly only
- * redraw the menu states if we're changing an application window.
- *
- ******************************************************************************)
-
- PROCEDURE DoActivate(window: WindowPtr; becomingActive, contextSwitch: BOOLEAN);
-
- BEGIN
- IF IsAppWindow(window) THEN
- BEGIN
- SetPort(window);
- IF NOT contextSwitch THEN
- InvalRect(window^.portRect);
- AdjustMenus;
- END;
- END; { DoActivate }
-
- {$S Main}
- (******************************************************************************
- *
- * Public: GetGlobalMouse
- *
- * This routine uses OSEventAvail with the kNoEvents constant to fill in the
- * event record with the current global mouse position and modifiers without
- * retrieving any events from the queue. You could do this with GetMouse
- * and LocalToGlobal, but that requires there to be a currently valid GrafPort
- * set (not a bad idea, but not always TRUE during initialization).
- *
- * We're not interested in the event, just the mouse position.
- *
- ******************************************************************************)
-
- PROCEDURE GetGlobalMouse(VAR mouse: Point);
-
- VAR
- event: EventRecord; { the event record we use }
-
- BEGIN
- IF OSEventAvail(kNoEvents, event) THEN
- ;
- mouse := event.where;
- END;
-
- {$S Main}
- (******************************************************************************
- *
- * Public: AdjustCursor
- *
- * Changes the Cursor, depending on its position. This also calculates the
- * Region where the current Cursor resides, for WaitNextEvent. We get a
- * "mouse moved" event when the Cursor is moved outside that Region, so then
- * we're called again and we recalculate the Region.
- *
- * This routine was called more frequently, and was more complicated, in
- * previous versions of Sample. It used to create a rectangular region based
- * on a window's portRect and intersect it with the visRgn. Since the visRgn
- * is in local coordinates but the contentRgn is in global coordinates, this
- * involved a fair amount of calculation, all of which was unnecessary because
- * a) we only do this for our frontmost window, which is always completely
- * visible (or both the contentRgn and visRgn are clipped off a monitor
- * equally), and
- * b) we can't move the mouse into any part of the content region that isn't
- * on-screen.
- *
- * So now we use the contentRgn of the front window for the region to use
- * the plus cursor in.
- *
- * If balloon help is on, and we're over the window's content region, we
- * call DoHelp to give other units a chance to display dynamic balloon help
- * while we're already calculating regions and mouse positions and such.
- * DoHelp changes the region we pass it to reflect when it would like
- * another chance to display a balloon, or when it would like the Help Manager
- * to remove the current balloon.
- *
- ******************************************************************************)
-
- PROCEDURE AdjustCursor(mouse: Point);
-
- VAR
- window: WindowPtr; { the front window }
- arrowRgn: RgnHandle; { region to use arrow cursor in }
- plusRgn: RgnHandle; { region to use plus cursor in }
- appWindow: BOOLEAN; { TRUE if this window is ours }
-
- BEGIN
- window := FrontWindow;
- appWindow := IsAppWindow(window);
- IF NOT gInBackground THEN { we only adjust the cursor when we are
- in front }
- BEGIN
-
- { calculate regions for different cursor shapes }
-
- arrowRgn := NewRgn;
- plusRgn := NewRgn;
-
- { start with a big, big rectangular region }
-
- SetRectRgn(arrowRgn, kExtremeNeg, kExtremeNeg, kExtremePos,
- kExtremePos);
- IF window <> NIL THEN
- SetPort(window); { for LocalToGlobal purposes inside DoHelp }
-
- { calculate plusRgn }
-
- IF appWindow THEN
- CopyRgn(WindowPeek(window)^.contRgn, plusRgn)
- ELSE
- SetEmptyRgn(plusRgn);
-
- { subtract plus region from arrowRgn }
-
- DiffRgn(arrowRgn, plusRgn, arrowRgn);
-
- { change the Cursor and the Region }
- IF PtInRgn(mouse, plusRgn) THEN
- BEGIN
- SetCursor(GetCursor(plusCursor)^^);
- CopyRgn(plusRgn, cursorRgn);
-
- { Give the window-supporting code a chance to do balloon help
- if we're over the window's content region. }
-
- IF gHasHelpMgr AND appWindow THEN
- IF HMGetBalloons THEN
- BEGIN
- DoHelp(mouse, cursorRgn);
- END
- END
- ELSE
- BEGIN
- SetCursor(arrow);
- CopyRgn(arrowRgn, cursorRgn);
- END;
-
- { get rid of our local regions }
-
- DisposeRgn(arrowRgn);
- DisposeRgn(plusRgn);
-
- END;
- END; { AdjustCursor }
-
- {$Z+} { makes this routine available for SampleUtilities.p }
- {$S Main}
- (******************************************************************************
- *
- * Public: DoEvent
- *
- * This routine does the right thing for all the events we can get.
- *
- ******************************************************************************)
-
- PROCEDURE DoEvent(event: EventRecord);
-
- VAR
- part, { window part from FindWindow }
- err: INTEGER; { result from DIBadMount }
- window: WindowPtr; { window we found an event in }
- ignore: BOOLEAN; { result from DoCloseWindow }
- key: CHAR; { key for keyDown/autoKey events }
- aPoint: Point; { Point for DIBadMount dialog }
- myErr: OSErr; { error from Apple Event Manager }
-
- BEGIN
- CASE event.what OF
- mouseDown:
- BEGIN
- part := FindWindow(event.where, window);
- CASE part OF
- inDesk: ;
- inMenuBar:
- BEGIN { process the menu command }
- AdjustMenus;
- SetCursor(arrow);
- DoMenuCommand(MenuSelect(event.where));
- AdjustCursor(event.where);
- END;
- inSysWindow: { let the system handle the mouseDown }
- SystemClick(event, window);
- inContent:
- BEGIN
- IF window <> FrontWindow THEN
- BEGIN
- SelectWindow(window);
-
- { If you want to process the click in the
- window as well as select it, call DoEvent
- again here. If you want that to work
- not only for your windows but when you're
- switched in by the Process Manager or
- MultiFinder, add the "GetFrontClicks" bit
- to your 'SIZE' resource flags }
-
- END
- ELSE
- DoContentClick(window, event);
- AdjustCursor(event.where);
- END;
- inDrag:
- BEGIN
- AdjustCursor(event.where);
- { pass screenBits.bounds to get all gDevices }
- DragWindow(window, event.where, screenBits.bounds);
- END;
- inGrow: ;
- inZoomIn, inZoomOut: ;
- inGoAway:
- IF TrackGoAway(window, event.where) THEN
- ignore := DoCloseWindow(window, kClosing);
- { we don't care if the user cancels this }
- END;
- END;
- keyDown, autoKey:
- BEGIN { check for MenuKey equivalents }
- key := CHR(BAND(event.message, charCodeMask));
- IF BAND(event.modifiers, cmdKey) <> 0 THEN { Command key down }
- IF event.what = keyDown THEN
- BEGIN
- AdjustMenus;
- DoMenuCommand(MenuKey(key));
- AdjustCursor(event.where);
- END;
- END;
- activateEvt:
- BEGIN
-
- { call DoActivate with the window and TRUE for activate, FALSE for
- deactivate }
-
- DoActivate(WindowPtr(event.message),
- BAND(event.modifiers,activeFlag) <> 0, FALSE);
- END;
- updateEvt: {call DoUpdate with the window to update}
-
- { call DoUpdate with the window to update }
-
- DoUpdate(WindowPtr(event.message));
- diskEvt:
-
- { Call DIBadMount in response to this so the user can format a floppy }
-
- IF HiWrd(event.message) <> noErr THEN
- BEGIN
- SetPt(aPoint, kDILeft, kDITop);
- err := DIBadMount(aPoint, event.message);
- END;
- osEvt:
- BEGIN
- AdjustCursor(event.where);
- CASE BAND(BRotL(event.message, 8), $FF) OF { high byte of message }
- SuspendResumeMessage:
-
- { set gInBackground based on whether we're suspending or
- resuming, then activate the front window accordingly }
-
- BEGIN
- gInBackground := (BAND(event.message, resumeFlag) = 0);
- DoActivate(FrontWindow, NOT gInBackground, TRUE);
- END;
- mouseMovedMessage:
- { We'd call AdjustCursor here, except we do it on all
- osEvt types so it's superfluous here }
- END;
- END;
- kHighLevelEvent:
- BEGIN
- IF gHasAppleEvents THEN
- BEGIN
- myErr := AEProcessAppleEvent(event);
- AdjustCursor(event.where);
- END;
- END;
- OTHERWISE; { prevents bad CASE label errors }
- END;
- END; { DoEvent }
-
- {$S Main}
- (******************************************************************************
- *
- * Public: EventLoop
- *
- * Get events forever and Handle them with DoEvent. Sample is only tested
- * under System 6.0.7 and later, and WaitNextEvent is always implemented
- * under System 6.0 and later, so we never call GetNextEvent.
- *
- ******************************************************************************)
-
- PROCEDURE EventLoop;
-
- VAR
- gotEvent: BOOLEAN; { TRUE if we got an event }
- event: EventRecord; { the event we're getting }
- mouse: Point; { the initial mouse position }
-
- BEGIN
- cursorRgn := NewRgn;
- GetGlobalMouse(mouse); { find the current mouse position }
- AdjustCursor(mouse); { set up the cursor once }
- REPEAT
-
- { put us "asleep" forever under MultiFinder or System 7 }
-
- gotEvent := WaitNextEvent(everyEvent, event, MAXLONGINT, cursorRgn);
-
- IF gotEvent THEN
- DoEvent(event);
-
- { If you are using modeless dialogs that have editText items, you'll
- want to call IsDialogEvent to give the caret a chance to blink,
- even if WaitNextEvent returns FALSE. However, be sure FrontWindow
- says there's a window before calling IsDialogEvent. }
-
- UNTIL gQuitting; { loop until our Terminate routine
- says to stop}
- END; { EventLoop }
-
- {$S Main}
- (******************************************************************************
- *
- * The main program entry point. Expand our heap, initialize, unload the
- * initialize segment and handle events forever.
- *
- ******************************************************************************)
-
- BEGIN
-
- {$IFC UNDEFINED Think_Pascal}
- UnloadSeg(@_DataInit); { note that _DataInit must not be in Main! }
- {$ENDC}
-
- { If you have stack requirements that differ from the default,
- then you could use SetApplLimit to increase stack space at
- this Point, before calling MaxApplZone. }
-
- MaxApplZone; { expand the heap so code segments load at
- the top}
-
- Initialize; { initialize the program }
- UnloadSeg(@Initialize); { note that Initialize must not be in Main! }
-
- EventLoop; { call the main event loop }
- END.
-